﻿using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Diagnostics.Contracts;
using System.Linq;
using IMModel.Domain.Common;

namespace IMModel.Domain
{
    /// <summary>
    /// Период
    /// </summary>
    partial class Term : ICacheble, IRespondentIndexVector, IItem
    {
        private double? _a;
        private int? _indexRequiredCount;
        private List<Respondent> _respondentCache;
        //private RespondentIndexCollection _respondentIndexCache;
        private double? _totalWeight;
        private double? _maxHarmony;


        interface IPremiumKResolver
        {
            double GetK(Term context, Respondent respondent);
        }

        class PremiumAResolverDefault : IPremiumKResolver
        {
            public double GetK(Term context, Respondent respondent)
            {
                Contract.Requires(context != null);
                Contract.Requires(respondent != null);
                Contract.Requires(respondent.IsParticipantInTerm(context));
                Contract.Requires(respondent.IsPremiumParticipant);
                return context.GetK(respondent);
            }
        }

        class PremiumKResolverSimple : IPremiumKResolver
        {
            public double GetK(Term context, Respondent respondent)
            {
                return 1;
            }

            public static readonly IPremiumKResolver Instance = new PremiumKResolverSimple();
        }

        private IPremiumKResolver _premiumKResolver = 
            new PremiumAResolverDefault();//PremiumKResolverSimple.Instance;

        /// <summary>
        /// Получает коэффициент коллективной ответственности
        /// </summary>
        public double T
        {
            get { return 1; }
        }

        private double MaxHarmony
        {
            get
            {
                Contract.Requires(Indexes.Count > 0);
                Contract.Ensures(_maxHarmony.HasValue);
                Contract.Ensures(_maxHarmony.Value > 0);
                if (!_maxHarmony.HasValue)
                {
                    _maxHarmony = 0;
                    foreach (var respondent in Participants)
                    {
                        double currMax = TopmostParticipateSectionTerms.Sum(x => x.Indexes[respondent]) /
                                         GetLeaderIndex(respondent);
                        if (currMax > _maxHarmony) _maxHarmony = currMax;
                    }
                }

                return _maxHarmony.Value;
            }
        }
        /// <summary>
        /// Получает абсолютный индекс гармонии респондента
        /// </summary>
        /// <param name="respondent">Респондент</param>
        /// <returns>Абсолютный индекс гармонии</returns>
        public double GetRespondentHarmonyIndexAbs(Respondent respondent)
        {
            Contract.Requires(respondent != null);
            Contract.Requires(respondent.IsParticipant);
            Contract.Requires(TopmostParticipateSectionTerms != null);
            Contract.Ensures(Contract.Result<double>() > 0);

            return TopmostParticipateSectionTerms.Sum(x => x.Indexes[respondent]) 
                / GetLeaderIndex(respondent) /
                TopmostParticipateSectionTerms.Count();
        }

        /// <summary>
        /// Получает коллекцию разделов верхнего уровня
        /// </summary>
        public IEnumerable<SectionTerm> TopmostSectionTerms
        {
            get
            {
                Contract.Requires(SectionTerms != null);
                Contract.Ensures(Contract.Result<IEnumerable<SectionTerm>>() != null);
                return GetTopmostSectionTerm();
            }
        }

        /// <summary>
        /// Получает перечень респондентов, участвующих в распределении надбавок
        /// </summary>
        public IEnumerable<Respondent> PremiumParticipants()
        {
            Contract.Requires(Indexes != null);
            return Indexes.Where(x => x.Key.IsPremiumParticipant).Select(x => x.Key);
        }

        /// <summary>
        /// Получает перечень респондентов, участвующих в распределении надбавок
        /// </summary>
        public IEnumerable<Respondent> PremiumParticipants(IndexContext context)
        {
            Contract.Requires(Indexes != null);
            return Indexes.Where(x => 
                context.IndexObjectFilter(x.Key)).Select(x => x.Key);
        }

        /// <summary>
        /// Получает перечень респондентов, участвующих в расчёте индексов
        /// </summary>
        public IEnumerable<Respondent> Participants
        {
            get
            {
                Contract.Requires(Indexes != null);
                return Indexes.Where(x => x.Key.IsParticipant).Select(x => x.Key);
            }
        }


        /// <summary>
        /// Получает коллекцию разделов верхнего уровня, которые участвуют в расчёте индексов
        /// </summary>
        public IEnumerable<SectionTerm> TopmostParticipateSectionTerms
        {
            get
            {
                Contract.Requires(TopmostSectionTerms != null);
                Contract.Ensures(Contract.Result<IEnumerable<SectionTerm>>() != null);
                return GetTopmostParticipateSectionTerms();
            }
        }

        /// <summary>
        /// Получает число показателей в данном периоде, которое необходимо респонденту для участия в индексной системе
        /// </summary>
        public int RequiredIndexCount
        {
            get
            {
                Contract.Requires(CharacteristicTerms != null);
                Contract.Ensures(_indexRequiredCount.HasValue);

                if (_indexRequiredCount == null)
                    _indexRequiredCount = CharacteristicTerms.Count(x => x.IndexRequired);
                return _indexRequiredCount.Value;
            }
        }

        /// <summary>
        /// Получает коллекцию респондентов в этом периоде
        /// </summary>
        public IEnumerable<Respondent> Respondents
        {
            get
            {
                Contract.Requires(DepartmentTerms != null);
                Contract.Ensures(Contract.Result<IEnumerable<Respondent>>() != null);

                /// было _respondentIndexCache...
                if (_respondentCache == null)
                {
                    _respondentCache = new List<Respondent>();
                    IEnumerable<EntitySet<Respondent>> respondents = DepartmentTerms.Select(x => x.Respondents);
                    foreach (var respondent in respondents)
                    {
                        _respondentCache.AddRange(respondent); //.Where(x => !x.Dismissed));
                    }
                }

                return _respondentCache.AsReadOnly();
            }
        }

        /// <summary>
        /// Получает нормировочный коэффициент "a" для расчёта надбавок респондентов;
        /// </summary>
        private double A
        {
            get
            {
                Contract.Requires(PremiumParticipants() != null);
                Contract.Requires(PremiumParticipants().Count() > 0);
                Contract.Ensures(_a.HasValue);
                Contract.Ensures(Contract.Result<double>() > 0);

                if (_a == null)
                    _a = 1 / PremiumParticipants().Sum(x => GetRespondentA(x));
                return _a.Value;
            }
        }

        #region IWeight Members

        /// <summary>
        /// Получает суммарный вес разделов верхнего уровня, участвующих в расчёте индексов
        /// </summary>
        public double TotalWeight
        {
            get
            {
                Contract.Assert(TopmostParticipateSectionTerms != null);
                if (_totalWeight == null) _totalWeight = TopmostParticipateSectionTerms.Sum(x => x.Weight);
                return _totalWeight.Value;
            }
        }

        #endregion

        /// <summary>
        /// Получает денежную надбавку респондента, основываясь на его показателях работы и величине фонда материального стимулирования
        /// </summary>
        /// <param name="respondent">Респондент</param>
        /// <returns>Надбавка респондента</returns>
        public decimal GetRespondentPremium(Respondent respondent)
        {
            Contract.Requires(respondent != null);
            Contract.Requires(respondent.IsPremiumParticipant);
            Contract.Requires(l1.HasValue);
            Contract.Requires(l2.HasValue);
            Contract.Requires(k1.HasValue);
            Contract.Requires(k2.HasValue);
            Contract.Requires(Founds.HasValue);
            Contract.Ensures(Contract.Result<decimal>() > 0);
            Contract.Ensures(CheckPremiumSpreadCorrect());
            try
            {
                return Convert.ToDecimal(A * T * GetRespondentA(respondent)) * Founds.Value;
            }
            catch (Exception)
            {
                return -1;
            }
        }

        private double GetRespondentA(Respondent respondent)
        {
            Contract.Requires(_premiumKResolver != null);
            Contract.Ensures(Contract.Result<double>() > 0);
            return GetRespondentAverageMarkWeight(respondent) * Indexes[respondent] * _premiumKResolver.GetK(this, respondent);
        }

        /// <summary>
        /// Получает усреднённый объём оценки деятельности непосредственным руководителем. Если
        /// оценка руководителя отсутствует, то положить значение равным 1
        /// </summary>
        /// <param name="respondent">Респондент</param>
        /// <returns>Усреднённый объём оценки деятельности</returns>
        private static double GetRespondentAverageMarkWeight(Respondent respondent)
        {
            Contract.Requires(respondent != null);
            Contract.Ensures(Contract.Result<double>() > 0);
            return respondent.HasMark ? respondent.AverageRespondentMarkVolume : 1;
        }

        #region PremiumEnsures
        const decimal PremiumSpreadPrecision = 0.1M;

        private bool CheckPremiumSpreadCorrect()
        {
            return Math.Abs(SpreadedPremium - Founds.Value) < PremiumSpreadPrecision;
        }

        private decimal SpreadedPremium
        {
            get
            {
                return Convert.ToDecimal(PremiumParticipants().Sum(x => x.Premium));
            }
        }
        #endregion

        /// <summary>
        /// Получает коэффициент от вклада индекса лидерства и гармонии респондента 
        /// </summary>
        /// <param name="respondent">Респондент</param>
        /// <returns>Коэффициент</returns>
        private double GetK(Respondent respondent)
        {
            Contract.Requires(respondent != null);
            Contract.Requires(respondent.IsPremiumParticipant);
            Contract.Ensures(Contract.Result<double>() > 0);
            return 1 + GetLeaderK(respondent) + GetHarmonyK(respondent);
        }

        private static double GetLk(double lVal, double iVal, double kVal)
        {
            Contract.Requires(lVal > 0);
            Contract.Requires(iVal > 0);
            Contract.Requires(kVal > 0);

            return lVal * (Math.Pow(iVal / 2 - 1, kVal));
        }

        private double GetHarmonyK(Respondent respondent)
        {
            Contract.Requires(respondent != null);
            Contract.Requires(l2.HasValue);
            Contract.Requires(k2.HasValue);
            return GetLk(l2.Value, GetHarmonyIndex(respondent), k2.Value);
        }

        private double GetLeaderK(Respondent respondent)
        {
            Contract.Requires(respondent != null);
            Contract.Requires(l1.HasValue);
            Contract.Requires(k1.HasValue);
            return GetLk(l1.Value, GetLeaderIndex(respondent), k1.Value);
        }

        private IEnumerable<SectionTerm> GetTopmostParticipateSectionTerms()
        {
            Contract.Requires(TopmostSectionTerms != null);
            return TopmostSectionTerms.Where(x => x.Participate);
        }

        private IEnumerable<SectionTerm> GetTopmostSectionTerm()
        {
            Contract.Requires(SectionTerms != null);
            Contract.Ensures(Contract.Result<IEnumerable<SectionTerm>>() != null);
            return SectionTerms.AsEnumerable().Where(x => x.IsTopmost);
        }



        /// <summary>
        /// Получает индекс гармонии заданного респондента
        /// </summary>
        /// <param name="respondent">Респондент</param>
        /// <returns>Индекс гармонии</returns>
        public double GetHarmonyIndex(Respondent respondent)
        {
            Contract.Requires(respondent != null);
            Contract.Requires(respondent.IsParticipant);
            return TopmostParticipateSectionTerms.Sum(x => x.Indexes[respondent])
                / GetLeaderIndex(respondent) / MaxHarmony;
        }

        /// <summary>
        /// Индекс лидерства заданного респондента
        /// </summary>
        /// <param name="respondent">Респондент</param>
        /// <returns>Индекс лидерства</returns>
        public double GetLeaderIndex(Respondent respondent)
        {
            Contract.Requires(respondent != null);
            Contract.Requires(respondent.IsParticipant);
            Contract.Requires(TopmostParticipateSectionTerms != null);
            return TopmostParticipateSectionTerms.Max(x => x.Indexes[respondent]);
        }

        /// <summary>
        /// Получает разделы, по которым лидирует заданный респондент
        /// </summary>
        /// <param name="respondent">Респондент</param>
        /// <returns>Разделы по которым лидирует респондент</returns>
        public IEnumerable<SectionTerm> GetLeaderIndexSections(Respondent respondent)
        {
            Contract.Requires(respondent != null);
            Contract.Requires(respondent.IsParticipant);
            Contract.Requires(TopmostParticipateSectionTerms != null);
            Contract.Ensures(Contract.Result<IEnumerable<SectionTerm>>() != null);
            double maxSectionIndex = GetLeaderIndex(respondent);
            return TopmostSectionTerms.Where(x => x.Indexes[respondent] == maxSectionIndex);
        }

        public override string ToString()
        {
            return string.Format("№{0}, {1} - {2}", Order, Start, Stop);
        }

        /// <summary>
        /// Получает родительский пункт. Для периода свойство возвращает null 
        /// </summary>
        public IItem Parent
        {
            get { return null; }
        }

        /// <summary>
        /// Получает вес пункта. Для периода свойство непременимо
        /// </summary>
        public double Weight
        {
            get { throw new InvalidOperationException("Не поддерживается"); }
        }

        /// <summary>
        /// Получает коллекцию дочерних пунктов
        /// </summary>
        public IEnumerable<IItem> Items
        {
            get { return TopmostParticipateSectionTerms; }
        }
        
        public IndexContext GetNewIndexContext(Func<IIndexObject, bool>
            filterPredicate, IAnswerVolumePolicy policy)
        {
            return new IndexContext(this, filterPredicate, policy);
        }
    }
}